package jmine.selenium;

import static jmine.tec.environment.utils.SpringConfigFactory.TEST_DATABASE_PROPERTY_NAME;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.List;

import jmine.selenium.services.SeleniumServicesPageExecutor;
import jmine.selenium.services.factory.SeleniumServiceFactory;
import jmine.tec.environment.db.DBEnvironment;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.execution.ServicesPageExecutor;
import jmine.tec.services.api.factory.ServiceFactory;
import jmine.tec.test.testcase.TestCaseException;
import bancosys.tec.services.ServicesIntegrationTestCase;

/**
 * Implementação base de testes de selenium.
 * 
 * @author Rafael Volpato
 * @date Nov 29, 2010
 */
public abstract class SeleniumTestCase extends ServicesIntegrationTestCase {

    public static final String SELENIUM_BROWSER = "selenium.browser";

    static {
        // Configurando variaveis necessarias para a execucao do teste.
        if (System.getProperty(TEST_DATABASE_PROPERTY_NAME) == null) {
            System.setProperty(TEST_DATABASE_PROPERTY_NAME, "propertydb");
        }

        if (System.getProperty(SELENIUM_BROWSER) == null) {
            System.setProperty(SELENIUM_BROWSER, "firefox");
        }
    }

    /*
     * XXX rafael.volpato: Bad boy! Static variables are evil! Keep it package protected! Don't even think about using it!
     */
    private static volatile EnvironmentConfiguration configuration;

    /**
     * Construtor.
     */
    public SeleniumTestCase() {
        super();
    }

    /**
     * Construtor.
     * 
     * @param url url
     */
    public SeleniumTestCase(URL url) {
        super(url);
    }

    /**
     * Retorna a configuração que deve ser utilizada para a execução do teste.
     * 
     * @return a configuração que deve ser utilizada para a execução do teste
     */
    public EnvironmentConfiguration getConfiguration() {
        if (SeleniumTestCase.configuration == null) {
            SeleniumTestCase.configuration = this.instantiateConfiguration();
        }
        return SeleniumTestCase.configuration;
    }

    /**
     * @param configuration the configuration to set
     */
    static void setConfiguration(EnvironmentConfiguration configuration) {
        SeleniumTestCase.configuration = configuration;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final void oncePerTestCaseClass() {
        if (!(this.getEnvironment() instanceof SeleniumEnvironment)) {
            SeleniumEnvironment env = new SeleniumEnvironment(this.getEnvironment(), this.getConfiguration());
            this.setEnvironment(env);
        }
        super.oncePerTestCaseClass();
    }

    /**
     * Instancia um ambiente.
     * 
     * @return um ambiente.
     */
    @Override
    protected final DBEnvironment instantiateEnvironment() {
        DBEnvironment delegate = super.instantiateEnvironment();
        return new SeleniumEnvironment(delegate, this.getConfiguration());
    }

    /**
     * Força a utilização do {@link SeleniumServicesPageExecutor} que foi configurado no spring
     * 
     * @return o {@link SeleniumServicesPageExecutor} que foi configurado no spring
     */
    @Override
    protected final ServicesPageExecutor getServicesPageExecutor() {
        // Utilizar o SeleneseServicesPageExecutor, mas ele não pode ser injetado pelo spring senão a base de dados fica inconsistente,
        // o ref-db utiliza o executor para executar serviços e colocar dados na base de referencia
        SeleniumServicesPageExecutor servicesPageExecutor =
                (SeleniumServicesPageExecutor) this.getHelper().getBeanFactory()
                        .getBean("seleniumServicesPageExecutor", SeleniumServicesPageExecutor.class);
        return servicesPageExecutor;
    }

    /**
     * Encontra os serviços a serem executados por Selenium. Seta a fábrica para o valor default para que os outros serviços inicialmente
     * não listados não rode via Selenium e sim por integração.
     * 
     * @param url url
     * @return List de ServicesPage
     * @throws Throwable Throwable
     */
    @Override
    protected final List<ServicesPage> getServicesPages(URL url) throws Throwable {
        List<ServicesPage> servicesPages = null;

        SeleniumServiceFactory seleniumServiceFactory;
        ServiceFactory defaultFactory = this.getServicesController().getServiceFactory();

        try {
            seleniumServiceFactory =
                    (SeleniumServiceFactory) this.getHelper().getBeanFactory()
                            .getBean("seleniumServiceFactory", SeleniumServiceFactory.class);

            seleniumServiceFactory.setEnvironmentConfiguration(this.getConfiguration());
            this.getServicesController().setServiceFactory(seleniumServiceFactory);

            // selenium service factory enabled
            servicesPages = super.getServicesPages(url);
        } finally {
            // selenium service factory disabled
            this.getServicesController().setServiceFactory(defaultFactory);
        }

        return servicesPages;
    }

    /**
     * Recriar a base de teste antes de inicializar cada teste.
     * 
     * @throws Exception se ocorrer algum erro
     */
    @Override
    public void setUp() throws Exception {
        super.setUp();
        this.getEnvironment().setRefdbType(this.getReferenceDatabaseDescriptionType());
        this.getEnvironment().restart();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String getControllerSpringId() {
        return "servicesController";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected List<String> getInputFiles() {
        try {
            return Collections.singletonList(new File(this.getTestInput().toURI()).toString());
        } catch (URISyntaxException e) {
            throw new TestCaseException(e);
        }
    }

    /**
     * Metodo utilizado para criar uma nova instancia da configuração padrão que deve ser utilizada na execução dos testes. Caso seja
     * informada uma outra configuração no construtor do teste, a configuração instanciada por esse metodo sera ignorada.
     * 
     * @return uma nova instancia de {@link EnvironmentConfiguration}
     */
    protected abstract EnvironmentConfiguration instantiateConfiguration();

}
